﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. 
// See License.txt in the project root for license information.


using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Fabric;
using System.Fabric.Description;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Actors;
using System.Threading.Actors.Utilities;
using System.Threading.Tasks;

namespace ActorGatewayService
{
    // An implementation of the Fabric based GatewayService.
    // A stateless service like Gateway service must implements IStatelessServiceInstance interface.
    public class ActorGatewayService : IStatelessServiceInstance
    {
        private ServiceInitializationParameters initializationParameters;
        private long instanceId;
        private Guid partitionId;

        private ActorGatewayTcpListener listener;

        public void Initialize(StatelessServiceInitializationParameters initializationParameters)
        {
            if (initializationParameters == null)
                throw new InvalidOperationException("Windows Fabric guarantees that initializationParameters is never null");

            this.initializationParameters = initializationParameters;
            this.instanceId = initializationParameters.InstanceId;
            this.partitionId = initializationParameters.PartitionId;
        }

        public Task<string> OpenAsync(IStatelessServicePartition partition, CancellationToken cancellationToken)
        {
            MessageLogger.WriteLine("GatewayService OpenAsync " + this.initializationParameters.ServiceName);

            int gatewayPort = 8505; // hard-code, for now
            MessageLogger.WriteLine("GatewayPort from config: {0}", gatewayPort);

            var result = StartGatewayListener(gatewayPort, AddressFamily.InterNetwork);

            var result2 = StartGatewayListener(gatewayPort, AddressFamily.InterNetworkV6);

            return NetworkConfiguration.PreferIPv6 ? result2 : result;
        }

        private Task<string> StartGatewayListener(int gatewayPort, AddressFamily addressFamily)
        {
            Uri gatewayBaseUri = GetBaseAddress(gatewayPort, addressFamily);

            // "Escape" any surrounding lock, since AGTL ctor could involve awaits.
            var result = Task.Factory.StartNew(() =>
            {
                this.listener = new ActorGatewayTcpListener(gatewayBaseUri, addressFamily);
                return gatewayBaseUri.ToString();
            });

            // If there were any problems associated with spinning up the listener, I want to know about them
            result.ContinueWith(antecedent =>
            {
                MessageLogger.WriteLine("ActorGatewayTcpListener for {1} failed to construct: {0}", antecedent.Exception.Message, addressFamily);
            }, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
            return result;
        }

        public System.Threading.Tasks.Task CloseAsync(CancellationToken cancellationToken)
        {
            MessageLogger.WriteLine("GatewayService Close " + this.initializationParameters.ServiceName);
            return Task.Factory.StartNew(() =>
                {
                    this.listener.Close();
                });
        }

        public void Abort()
        {
            MessageLogger.WriteLine("GatewayService Abort " + this.initializationParameters.ServiceName);
            this.listener.Close();
        }

        public System.Collections.Generic.IEnumerable<Uri> ListenerPrefixes
        {
            get { { return Enumerable.Empty<Uri>(); } }
        }

        #region Constructor
        // Constructor for the GatewayService.  When created, this service
        // is given the URI name that the administrator defined
        public ActorGatewayService()
            : base()
        {
        }
        #endregion

        // Convert user provided string to URI
        private static Uri GetBaseAddress(int portNumber, AddressFamily addressFamily)
        {
            Contract.Requires(addressFamily == AddressFamily.InterNetworkV6 || addressFamily == AddressFamily.InterNetwork);

            var ipAddress = NetworkConfiguration.GetIPAddress(addressFamily);

            UriBuilder uriBuilder = new UriBuilder(
                Uri.UriSchemeNetTcp,
                ipAddress.ToString(),
                portNumber);

            return uriBuilder.Uri;
        }
    }
}
